/*
 * Copyright (C) 2008,2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

/*
 * Implementation
 */
void
ebuiltin_store_pointer(evalue_t *value)
{
    evalue_t	*local;
    evector_t	*vector;
    eint32_t	 integer;

    switch (value->t) {
	case t_anonref:
	case t_globalref:
	    if (*value->v.v == null ||
		eobject_type(*value->v.v) != thread_self->ret.t) {
	    create_object:
		switch (thread_self->ret.t) {
		    case t_int:
			enew_integer((eobject_t *)value->v.v,
				     thread_self->ret.v.i);
			break;
		    case t_float:
			enew_float((eobject_t *)value->v.v,
				   thread_self->ret.v.d);
			break;
		    case t_mpz:
			enew_mpz((eobject_t *)value->v.v, thr_zr);
			break;
		    case t_mpq:
			enew_mpq((eobject_t *)value->v.v, thr_qr);
			break;
		    case t_mpr:
			enew_mpr((eobject_t *)value->v.v, thr_rr);
			break;
		    case t_cdd:
			enew_cdd((eobject_t *)&value->v.cdd,
				 thread_self->ret.v.cdd);
			break;
		    case t_cqq:
			enew_cqq((eobject_t *)value->v.v, thr_qq);
			break;
		    case t_mpc:
			enew_mpc((eobject_t *)value->v.v, thr_c);
			break;
		    default:
			enew_vector((evector_t **)value->v.v, t_uint8,
				    thread_self->vec->length, 1);
			vector = *(evector_t **)value->v.v;
			memcpy(vector->v.obj, thread_self->vec->v.obj,
			       thread_self->vec->length);
			break;
		}
	    }
	    else {
		switch (thread_self->ret.t) {
		    case t_int:
			*(eint_t *)(*value->v.v) = thread_self->ret.v.i;
			break;
		    case t_float:
			*(efloat_t *)(*value->v.v) = thread_self->ret.v.d;
			break;
		    case t_mpz:
			mpz_set((empz_t)*value->v.v, thr_zr);
			break;
		    case t_mpq:
			mpq_set((empq_t)*value->v.v, thr_qr);
			break;
		    case t_mpr:
			empr_set((empr_t)*value->v.v, thr_rr);
			break;
		    case t_cdd:
			*(ecdd_t *)(*value->v.v) = thread_self->ret.v.cdd;
			break;
		    case t_cqq:
			cqq_set((ecqq_t)*value->v.v, thr_qq);
			break;
		    case t_mpc:
			empc_set((empc_t)*value->v.v, thr_c);
			break;
		    default:
			goto create_object;
		}
	    }
	    break;
	case t_localref:
	    local = value->v.loc->thread->ess + value->v.loc->offset;
	    if (value >= local->v.loc->thread->esp ||
		(value->t == t_void && value->v.o)) {
		ewarn("invalid stack object reference");
		goto done;
	    }
	    goto store_stack;
	case t_stackref:
	    value = value->v.o;
	store_stack:
	    switch (thread_self->ret.t) {
		case t_int:
		    value->v.i = thread_self->ret.v.i;
		    value->t = t_int;
		    break;
		case t_float:
		    value->v.d = thread_self->ret.v.d;
		    value->t = t_float;
		    break;
		case t_mpz:
		    if (value->t == t_mpz)
			mpz_set(value->v.mpz, thr_zr);
		    else {
			enew_mpz(&value->v.o, thr_zr);
			value->t = t_mpz;
		    }
		    break;
		case t_mpq:
		    if (value->t == t_mpq)
			mpq_set(value->v.mpq, thr_qr);
		    else {
			enew_mpq(&value->v.o, thr_qr);
			value->t = t_mpq;
		    }
		    break;
		case t_mpr:
		    if (value->t == t_mpr)
			empr_set(value->v.mpr, thr_rr);
		    else {
			enew_mpr(&value->v.o, thr_rr);
			value->t = t_mpr;
		    }
		    break;
		case t_cdd:
		    value->v.cdd = thread_self->ret.v.cdd;
		    value->t = t_cdd;
		    break;
		case t_cqq:
		    if (value->t == t_cqq)
			cqq_set(value->v.cqq, thr_qq);
		    else {
			enew_cqq(&value->v.o, thr_qq);
			value->t = t_cqq;
		    }
		    break;
		case t_mpc:
		    if (value->t == t_mpc)
			empc_set(value->v.mpc, thr_c);
		    else {
			enew_mpc(&value->v.o, thr_c);
			value->t = t_mpc;
		    }
		    break;
		default:
		    enew_vector(&value->v.V, t_uint8,
				thread_self->vec->length, 1);
		    vector = value->v.o;
		    memcpy(vector->v.obj, thread_self->vec->v.obj,
			   thread_self->vec->length);
		    value->t = t_string;
		    break;
	    }
	    break;
	case t_t_localref:
	    local = value->v.loc->thread->ess + value->v.loc->offset;
	    if (value >= local->v.loc->thread->esp ||
		(value->t == t_void && value->v.o)) {
		ewarn("invalid stack object reference");
		goto done;
	    }
	    goto store_t_stack;
	case t_t_stackref:
	    value = value->v.o;
	store_t_stack:
	    switch (thread_self->ret.t) {
		case t_int:
		    switch (value->t) {
			case t_int8:	case t_uint8:
			    value->v.i8 = thread_self->ret.v.i;
			    break;
			case t_int16:	case t_uint16:
			    value->v.i16 = thread_self->ret.v.i;
			    break;
			case t_int32:	case t_uint32:
			    value->v.i32 = thread_self->ret.v.i;
			    break;
			case t_int64:	case t_uint64:
			    value->v.i64 = thread_self->ret.v.i;
			    break;
			case t_float32:
			    value->v.f32 = thread_self->ret.v.i;
			    break;
			case t_float64:
			    value->v.f64 = thread_self->ret.v.i;
			    break;
			default:
			    value->v.i = thread_self->ret.v.i;
			    value->t = t_int;
		    }
		    break;
		case t_float:
		    switch (value->t) {
			case t_int8:	case t_uint8:
			    value->v.i8 = thread_self->ret.v.d;
			    break;
			case t_int16:	case t_uint16:
			    value->v.i16 = thread_self->ret.v.d;
			    break;
			case t_int32:	case t_uint32:
			    value->v.i32 = thread_self->ret.v.d;
			    break;
			case t_int64:	case t_uint64:
			    value->v.i64 = thread_self->ret.v.d;
			    break;
			case t_float32:
			    value->v.f32 = thread_self->ret.v.d;
			    break;
			case t_float64:
			    value->v.f64 = thread_self->ret.v.d;
			    break;
			default:
			    value->v.d = thread_self->ret.v.d;
			    value->t = t_float;
		    }
		    break;
		case t_mpz:
		    switch (value->t) {
			case t_int8:	case t_uint8:
			    value->v.i8 = empz_get_i(thr_zr);
			    break;
			case t_int16:	case t_uint16:
			    value->v.i16 = empz_get_i(thr_zr);
			    break;
			case t_int32:	case t_uint32:
			    value->v.i32 = empz_get_i(thr_zr);
			    break;
			case t_int64:	case t_uint64:
			    value->v.i64 = empz_get_i(thr_zr);
			    break;
			case t_float32:
			    value->v.f32 = mpz_get_d(thr_zr);
			    break;
			case t_float64:
			    value->v.f64 = mpz_get_d(thr_zr);
			    break;
			case t_mpz:
			    mpz_set(value->v.mpz, thr_zr);
			    break;
			default:
			    enew_mpz(&value->v.o, thr_zr);
			    value->t = t_mpz;
		    }
		    break;
		case t_mpq:
		    switch (value->t) {
			case t_int8:	case t_uint8:
			    value->v.i8 = empq_get_i(thr_qr);
			    break;
			case t_int16:	case t_uint16:
			    value->v.i16 = empq_get_i(thr_qr);
			    break;
			case t_int32:	case t_uint32:
			    value->v.i32 = empq_get_i(thr_qr);
			    break;
			case t_int64:	case t_uint64:
			    value->v.i64 = empq_get_i(thr_qr);
			    break;
			case t_float32:
			    value->v.f32 = mpq_get_d(thr_qr);
			    break;
			case t_float64:
			    value->v.f64 = mpq_get_d(thr_qr);
			    break;
			case t_mpq:
			    mpq_set(value->v.mpq, thr_qr);
			    break;
			default:
			    enew_mpq(&value->v.o, thr_qr);
			    value->t = t_mpq;
		    }
		    break;
		case t_mpr:
		    switch (value->t) {
			case t_int8:	case t_uint8:
			    value->v.i8 = empr_get_i(thr_rr);
			    break;
			case t_int16:	case t_uint16:
			    value->v.i16 = empr_get_i(thr_rr);
			    break;
			case t_int32:	case t_uint32:
			    value->v.i32 = empr_get_i(thr_rr);
			    break;
			case t_int64:	case t_uint64:
			    value->v.i64 = empr_get_i(thr_rr);
			    break;
			case t_float32:
			    value->v.f32 = mpfr_get_d(thr_rr, thr_rnd);
			    break;
			case t_float64:
			    value->v.f64 = mpfr_get_d(thr_rr, thr_rnd);
			    break;
			case t_mpr:
			    empr_set(value->v.mpr, thr_rr);
			    break;
			default:
			    enew_mpr(&value->v.o, thr_rr);
			    value->t = t_mpr;
		    }
		    break;
		case t_cdd:
		    switch (value->t) {
			case t_int8:	case t_uint8:
			    value->v.i8 = real(thread_self->ret.v.cdd);
			    break;
			case t_int16:	case t_uint16:
			    value->v.i16 = real(thread_self->ret.v.cdd);
			    break;
			case t_int32:	case t_uint32:
			    value->v.i32 = real(thread_self->ret.v.cdd);
			    break;
			case t_int64:	case t_uint64:
			    value->v.i64 = real(thread_self->ret.v.cdd);
			    break;
			case t_float32:
			    value->v.f32 = real(thread_self->ret.v.cdd);
			    break;
			case t_float64:
			    value->v.f64 = real(thread_self->ret.v.cdd);
			    break;
			default:
			    value->v.cdd = thread_self->ret.v.cdd;
			    value->t = t_cdd;
		    }
		    break;
		case t_cqq:
		    switch (value->t) {
			case t_int8:	case t_uint8:
			    value->v.i8 = empq_get_i(thr_qr);
			    break;
			case t_int16:	case t_uint16:
			    value->v.i16 = empq_get_i(thr_qr);
			    break;
			case t_int32:	case t_uint32:
			    value->v.i32 = empq_get_i(thr_qr);
			    break;
			case t_int64:	case t_uint64:
			    value->v.i64 = empq_get_i(thr_qr);
			    break;
			case t_float32:
			    value->v.f32 = mpq_get_d(thr_qr);
			    break;
			case t_float64:
			    value->v.f64 = mpq_get_d(thr_qr);
			    break;
			case t_cqq:
			    cqq_set(value->v.cqq, thr_qq);
			    break;
			default:
			    enew_cqq(&value->v.o, thr_qq);
			    value->t = t_cqq;
		    }
		    break;
		case t_mpc:
		    switch (value->t) {
			case t_int8:	case t_uint8:
			    value->v.i8 = empr_get_i(thr_rr);
			    break;
			case t_int16:	case t_uint16:
			    value->v.i16 = empr_get_i(thr_rr);
			    break;
			case t_int32:	case t_uint32:
			    value->v.i32 = empr_get_i(thr_rr);
			    break;
			case t_int64:	case t_uint64:
			    value->v.i64 = empr_get_i(thr_rr);
			    break;
			case t_float32:
			    value->v.f32 = mpfr_get_d(thr_rr, thr_rnd);
			    break;
			case t_float64:
			    value->v.f64 = mpfr_get_d(thr_rr, thr_rnd);
			    break;
			case t_mpc:
			    empc_set(value->v.mpc, thr_c);
			    break;
			default:
			    enew_mpc(&value->v.o, thr_c);
			    value->t = t_mpr;
		    }
		    break;
		default:
		    switch (value->t) {
			case t_int8:	case t_uint8:
			case t_int16:	case t_uint16:
			case t_int32:	case t_uint32:
			case t_int64:	case t_uint64:
			case t_float32:	case t_float64:
			    eraise_exception(except_type_mismatch);
			default:
			    enew_vector(&value->v.V, t_uint8,
					thread_self->vec->length, 1);
			    vector = value->v.o;
			    memcpy(vector->v.obj, thread_self->vec->v.obj,
				   thread_self->vec->length);
			    value->t = t_string;
		    }
	    }
	    break;
	default:
	    if (!(value->t & t_pointer)) {
		ewarn("not a pointer");
		goto done;
	    }
	    value->t &= ~t_pointer;
	    integer = value->v.V->offset;
	    value->v.o = value->v.V->v.obj;
	    if (value->t & t_vector) {
		if (integer < 0 || integer >= value->v.V->length) {
		    ewarn("offset out of bounds");
		    goto done;
		}
		switch (eobject_type(value->v.o) & ~t_vector) {
		    case t_void:
		    default:
			value->v.o = *value->v.v + integer;
			goto create_object;
			break;
		    case t_int8: case t_uint8:
			value->v.o = *value->v.ppi8 + integer;
			goto store_int8;
			break;
		    case t_int16: case t_uint16:
			value->v.o = *value->v.ppi16 + integer;
			goto store_int16;
			break;
		    case t_int32: case t_uint32:
			value->v.o = *value->v.ppi32 + integer;
			goto store_int32;
			break;
		    case t_int64: case t_uint64:
			value->v.o = *value->v.ppi64 + integer;
			goto store_int64;
			break;
		    case t_float32:
			value->v.o = *value->v.ppf32 + integer;
			goto store_float32;
			break;
		    case t_float:
			value->v.o = *value->v.ppf64 + integer;
			goto store_float64;
		}
	    }
	    else {
		value->v.o = value->v.r + integer;
		switch (value->t) {
		    case t_int8: case t_uint8:
		    store_int8:
			switch (thread_self->ret.t) {
			    case t_int:
				*(eint8_t *)value->v.o = thread_self->ret.v.i;
				break;
			    case t_float:
				*(eint8_t *)value->v.o = thread_self->ret.v.d;
				break;
			    case t_mpz:
				*(eint8_t *)value->v.o = empz_get_i(thr_zr);
				break;
			    case t_mpq:		case t_cqq:
				*(eint8_t *)value->v.o = empq_get_i(thr_qr);
				break;
			    case t_mpr:
 				*(eint8_t *)value->v.o = empr_get_i(thr_rr);
 				break;
			    case t_mpc:
				*(eint8_t *)value->v.o = empr_get_i(thr_rr);
				break;
			    default:
				ewarn("type coercion error");
				break;
			}
			break;
		    case t_int16: case t_uint16:
		    store_int16:
			switch (thread_self->ret.t) {
			    case t_int:
				*(eint16_t *)value->v.o = thread_self->ret.v.i;
				break;
			    case t_float:
				*(eint16_t *)value->v.o = thread_self->ret.v.d;
				break;
			    case t_mpz:
				*(eint16_t *)value->v.o = empz_get_i(thr_zr);
				break;
			    case t_mpq:		case t_cqq:
				*(eint16_t *)value->v.o = empq_get_i(thr_qr);
				break;
			    case t_mpr:
 				*(eint16_t *)value->v.o = empr_get_i(thr_rr);
 				break;
			    case t_mpc:
				*(eint16_t *)value->v.o = empr_get_i(thr_rr);
				break;
			    default:
				ewarn("type coercion error");
				break;
			}
			break;
		    case t_int32: case t_uint32:
		    store_int32:
			switch (thread_self->ret.t) {
			    case t_int:
				*(eint32_t *)value->v.o = thread_self->ret.v.i;
				break;
			    case t_float:
	 			*(eint32_t *)value->v.o = thread_self->ret.v.d;
				break;
			    case t_mpz:
				*(eint32_t *)value->v.o = empz_get_i(thr_zr);
				break;
			    case t_mpq:		case t_cqq:
				*(eint32_t *)value->v.o = empq_get_i(thr_qr);
				break;
			    case t_mpr:
 				*(eint32_t *)value->v.o = empr_get_i(thr_rr);
 				break;
			    case t_mpc:
				*(eint32_t *)value->v.o = empr_get_i(thr_rr);
				break;
			    default:
				ewarn("type coercion error");
				break;
			}
			break;
		    case t_int64: case t_uint64:
		    store_int64:
			switch (thread_self->ret.t) {
			    case t_int:
				*(eint64_t *)value->v.o = thread_self->ret.v.i;
				break;
			    case t_float:
				*(eint64_t *)value->v.o = thread_self->ret.v.d;
				break;
			    case t_mpz:
				*(eint64_t *)value->v.o = empz_get_i(thr_zr);
				break;
			    case t_mpq:		case t_cqq:
				*(eint64_t *)value->v.o = empq_get_i(thr_qr);
				break;
			    case t_mpr:
				*(eint64_t *)value->v.o = empr_get_i(thr_rr);
				break;
			    case t_mpc:
				*(eint64_t *)value->v.o = empr_get_i(thr_rr);
				break;
			    default:
				ewarn("type coercion error");
				break;
			}
			break;
		    case t_float32:
		    store_float32:
			switch (thread_self->ret.t) {
			    case t_int:
				*(efloat32_t *)value->v.o = thread_self->ret.v.i;
				break;
			    case t_float:
				*(efloat32_t *)value->v.o = thread_self->ret.v.d;
				break;
			    case t_mpz:
				*(efloat32_t *)value->v.o = mpz_get_d(thr_zr);
				break;
			    case t_mpq:		case t_cqq:
				*(efloat32_t *)value->v.o = mpq_get_d(thr_qr);
				break;
			    case t_mpr:
				*(efloat32_t *)value->v.o = mpfr_get_d(thr_rr, thr_rnd);
				break;
			    case t_mpc:
				*(efloat32_t *)value->v.o = mpfr_get_d(thr_rr, thr_rnd);
				break;
			    default:
				ewarn("type coercion error");
				break;
			}
			break;
		    case t_float64:
		    store_float64:
			switch (thread_self->ret.t) {
			    case t_int:
				*(efloat64_t *)value->v.o = thread_self->ret.v.i;
				break;
			    case t_float64:
				*(efloat64_t *)value->v.o = thread_self->ret.v.d;
				break;
			    case t_mpz:
				*(efloat64_t *)value->v.o = mpz_get_d(thr_zr);
				break;
			    case t_mpq:		case t_cqq:
				*(efloat64_t *)value->v.o = mpq_get_d(thr_qr);
				break;
			    case t_mpr:
				*(efloat64_t *)value->v.o = mpfr_get_d(thr_rr, thr_rnd);
				break;
			    case t_mpc:
				*(efloat64_t *)value->v.o = mpfr_get_d(thr_rr, thr_rnd);
				break;
			    default:
				ewarn("type coercion error");
				break;
			}
			break;
		    default:
			goto create_object;
		}
	    }
	    break;
    }

done:;
}
